<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>fromCharCode vs fromCodePoint 区别详解</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      padding: 20px;
    }

    .container {
      max-width: 1200px;
      margin: 0 auto;
      background: white;
      border-radius: 15px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
      overflow: hidden;
    }

    .header {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 40px;
      text-align: center;
    }

    .header h1 {
      font-size: 2.5em;
      margin-bottom: 10px;
      font-weight: 300;
    }

    .header p {
      font-size: 1.2em;
      opacity: 0.9;
    }

    .content {
      padding: 40px;
    }

    .section {
      margin-bottom: 40px;
      padding: 30px;
      border-radius: 10px;
      background: #f8f9fa;
      border-left: 5px solid #667eea;
    }

    .section h2 {
      color: #333;
      margin-bottom: 20px;
      font-size: 1.8em;
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .comparison-table {
      width: 100%;
      border-collapse: collapse;
      margin: 20px 0;
      background: white;
      border-radius: 8px;
      overflow: hidden;
      box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    }

    .comparison-table th,
    .comparison-table td {
      padding: 15px;
      text-align: left;
      border-bottom: 1px solid #e9ecef;
    }

    .comparison-table th {
      background: #667eea;
      color: white;
      font-weight: 600;
    }

    .comparison-table tr:nth-child(even) {
      background: #f8f9fa;
    }

    .comparison-table tr:hover {
      background: #e3f2fd;
    }

    .demo-section {
      background: white;
      padding: 25px;
      border-radius: 8px;
      margin: 20px 0;
      border: 2px solid #e9ecef;
    }

    .demo-title {
      font-size: 1.3em;
      font-weight: 600;
      color: #333;
      margin-bottom: 15px;
      padding-bottom: 10px;
      border-bottom: 2px solid #667eea;
    }

    .code-block {
      background: #2d3748;
      color: #e2e8f0;
      padding: 20px;
      border-radius: 8px;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
      font-size: 14px;
      line-height: 1.6;
      overflow-x: auto;
      margin: 15px 0;
    }

    .code-comment {
      color: #68d391;
    }

    .code-string {
      color: #fbb6ce;
    }

    .code-number {
      color: #90cdf4;
    }

    .code-keyword {
      color: #f6ad55;
    }

    .result-display {
      background: #e8f5e8;
      border: 2px solid #4caf50;
      border-radius: 8px;
      padding: 15px;
      margin: 15px 0;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
      font-size: 14px;
    }

    .error-display {
      background: #ffebee;
      border: 2px solid #f44336;
      border-radius: 8px;
      padding: 15px;
      margin: 15px 0;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
      font-size: 14px;
      color: #c62828;
    }

    .warning-display {
      background: #fff3e0;
      border: 2px solid #ff9800;
      border-radius: 8px;
      padding: 15px;
      margin: 15px 0;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
      font-size: 14px;
      color: #e65100;
    }

    .interactive-demo {
      background: white;
      padding: 25px;
      border-radius: 8px;
      margin: 20px 0;
      border: 2px solid #e9ecef;
    }

    .input-group {
      margin-bottom: 20px;
    }

    .input-group label {
      display: block;
      margin-bottom: 8px;
      font-weight: 600;
      color: #555;
    }

    .input-group input {
      width: 100%;
      padding: 12px;
      border: 2px solid #e1e5e9;
      border-radius: 6px;
      font-size: 16px;
      font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
    }

    .input-group input:focus {
      outline: none;
      border-color: #667eea;
      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    }

    .btn {
      padding: 12px 24px;
      border: none;
      border-radius: 6px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s ease;
      margin-right: 10px;
      margin-bottom: 10px;
    }

    .btn-primary {
      background: #667eea;
      color: white;
    }

    .btn-primary:hover {
      background: #5a67d8;
      transform: translateY(-2px);
    }

    .btn-secondary {
      background: #f8f9fa;
      color: #495057;
      border: 2px solid #e9ecef;
    }

    .btn-secondary:hover {
      background: #e9ecef;
    }

    .feature-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 20px;
      margin: 20px 0;
    }

    .feature-card {
      background: white;
      padding: 20px;
      border-radius: 8px;
      border: 2px solid #e9ecef;
      transition: transform 0.3s ease;
    }

    .feature-card:hover {
      transform: translateY(-5px);
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
    }

    .feature-card h3 {
      color: #333;
      margin-bottom: 10px;
      font-size: 1.2em;
    }

    .feature-card p {
      color: #666;
      line-height: 1.6;
    }

    .unicode-examples {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 15px;
      margin: 20px 0;
    }

    .unicode-item {
      background: white;
      padding: 15px;
      border-radius: 6px;
      text-align: center;
      border: 1px solid #e9ecef;
    }

    .unicode-char {
      font-size: 2em;
      margin-bottom: 10px;
    }

    .unicode-info {
      font-size: 0.9em;
      color: #666;
    }

    @media (max-width: 768px) {
      .content {
        padding: 20px;
      }

      .section {
        padding: 20px;
      }

      .feature-grid {
        grid-template-columns: 1fr;
      }

      .unicode-examples {
        grid-template-columns: repeat(2, 1fr);
      }
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="header">
      <h1>🔤 fromCharCode vs fromCodePoint</h1>
      <p>深入理解JavaScript中字符编码转换方法的区别</p>
    </div>

    <div class="content">
      <!-- 概述部分 -->
      <div class="section">
        <h2>📋 概述</h2>
        <p>JavaScript提供了两种将Unicode码点转换为字符的方法：<code>String.fromCharCode()</code> 和
          <code>String.fromCodePoint()</code>。它们在处理Unicode字符时有重要区别。</p>

        <table class="comparison-table">
          <thead>
            <tr>
              <th>特性</th>
              <th>fromCharCode</th>
              <th>fromCodePoint</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><strong>引入时间</strong></td>
              <td>ES1 (1997)</td>
              <td>ES6 (2015)</td>
            </tr>
            <tr>
              <td><strong>Unicode范围</strong></td>
              <td>0-65535 (U+0000-U+FFFF)</td>
              <td>0-1114111 (U+0000-U+10FFFF)</td>
            </tr>
            <tr>
              <td><strong>代理对处理</strong></td>
              <td>❌ 不支持</td>
              <td>✅ 自动处理</td>
            </tr>
            <tr>
              <td><strong>参数类型</strong></td>
              <td>16位整数</td>
              <td>32位整数</td>
            </tr>
            <tr>
              <td><strong>错误处理</strong></td>
              <td>静默截断</td>
              <td>抛出RangeError</td>
            </tr>
          </tbody>
        </table>
      </div>

      <!-- 基本用法对比 -->
      <div class="section">
        <h2>🔧 基本用法对比</h2>

        <div class="demo-section">
          <div class="demo-title">基本字符转换</div>
          <div class="code-block">
            <span class="code-comment">// 基本ASCII字符 (0-127)</span>
            <span class="code-keyword">const</span> char1 = <span class="code-keyword">String</span>.fromCharCode(<span
              class="code-number">65</span>); <span class="code-comment">// 'A'</span>
            <span class="code-keyword">const</span> char2 = <span class="code-keyword">String</span>.fromCodePoint(<span
              class="code-number">65</span>); <span class="code-comment">// 'A'</span>

            <span class="code-comment">// 基本多字节字符 (128-65535)</span>
            <span class="code-keyword">const</span> char3 = <span class="code-keyword">String</span>.fromCharCode(<span
              class="code-number">0x4E2D</span>); <span class="code-comment">// '中'</span>
            <span class="code-keyword">const</span> char4 = <span class="code-keyword">String</span>.fromCodePoint(<span
              class="code-number">0x4E2D</span>); <span class="code-comment">// '中'</span>

            console.log(char1, char2); <span class="code-comment">// A A</span>
            console.log(char3, char4); <span class="code-comment">// 中 中</span>
          </div>
          <div class="result-display" id="basicResult">点击"运行基本示例"查看结果</div>
          <button class="btn btn-primary" onclick="runBasicExample()">运行基本示例</button>
        </div>
      </div>

      <!-- 代理对处理 -->
      <div class="section">
        <h2>🎯 代理对处理差异</h2>

        <div class="demo-section">
          <div class="demo-title">处理超出BMP范围的字符 (U+10000-U+10FFFF)</div>
          <div class="code-block">
            <span class="code-comment">// 笑脸emoji: U+1F600</span>
            <span class="code-keyword">const</span> emojiCode = <span class="code-number">0x1F600</span>;

            <span class="code-comment">// fromCharCode 无法正确处理</span>
            <span class="code-keyword">const</span> fromCharCode = <span
              class="code-keyword">String</span>.fromCharCode(emojiCode);
            console.log(fromCharCode); <span class="code-comment">// 显示为两个字符或乱码</span>

            <span class="code-comment">// fromCodePoint 正确处理</span>
            <span class="code-keyword">const</span> fromCodePoint = <span
              class="code-keyword">String</span>.fromCodePoint(emojiCode);
            console.log(fromCodePoint); <span class="code-comment">// 😀</span>

            <span class="code-comment">// 检查字符长度</span>
            console.log(fromCharCode.length); <span class="code-comment">// 2 (错误)</span>
            console.log(fromCodePoint.length); <span class="code-comment">// 1 (正确)</span>
          </div>
          <div class="result-display" id="surrogateResult">点击"运行代理对示例"查看结果</div>
          <button class="btn btn-primary" onclick="runSurrogateExample()">运行代理对示例</button>
        </div>
      </div>

      <!-- 错误处理 -->
      <div class="section">
        <h2>⚠️ 错误处理差异</h2>

        <div class="demo-section">
          <div class="demo-title">无效码点的处理</div>
          <div class="code-block">
            <span class="code-comment">// 超出范围的码点</span>
            <span class="code-keyword">const</span> invalidCode = <span class="code-number">0x200000</span>; <span
              class="code-comment">// 超出Unicode范围</span>

            <span class="code-comment">// fromCharCode 静默处理</span>
            <span class="code-keyword">try</span> {
            <span class="code-keyword">const</span> result1 = <span
              class="code-keyword">String</span>.fromCharCode(invalidCode);
            console.log(<span class="code-string">'fromCharCode:'</span>, result1);
            } <span class="code-keyword">catch</span> (e) {
            console.log(<span class="code-string">'fromCharCode error:'</span>, e.message);
            }

            <span class="code-comment">// fromCodePoint 抛出错误</span>
            <span class="code-keyword">try</span> {
            <span class="code-keyword">const</span> result2 = <span
              class="code-keyword">String</span>.fromCodePoint(invalidCode);
            console.log(<span class="code-string">'fromCodePoint:'</span>, result2);
            } <span class="code-keyword">catch</span> (e) {
            console.log(<span class="code-string">'fromCodePoint error:'</span>, e.message);
            }
          </div>
          <div class="result-display" id="errorResult">点击"运行错误处理示例"查看结果</div>
          <button class="btn btn-primary" onclick="runErrorExample()">运行错误处理示例</button>
        </div>
      </div>

      <!-- 实际应用场景 -->
      <div class="section">
        <h2>💡 实际应用场景</h2>

        <div class="feature-grid">
          <div class="feature-card">
            <h3>🎨 Emoji处理</h3>
            <p>处理现代Unicode字符，如emoji、特殊符号等超出BMP范围的字符时，应使用fromCodePoint。</p>
            <div class="code-block">
              <span class="code-comment">// 推荐使用 fromCodePoint</span>
              <span class="code-keyword">const</span> emoji = <span
                class="code-keyword">String</span>.fromCodePoint(<span class="code-number">0x1F600</span>);
            </div>
          </div>

          <div class="feature-card">
            <h3>🌍 国际化</h3>
            <p>处理各种语言的Unicode字符时，fromCodePoint提供更好的兼容性和错误处理。</p>
            <div class="code-block">
              <span class="code-comment">// 处理复杂Unicode字符</span>
              <span class="code-keyword">const</span> char = <span
                class="code-keyword">String</span>.fromCodePoint(<span class="code-number">0x1F1FA</span>, <span
                class="code-number">0x1F1F8</span>);
            </div>
          </div>

          <div class="feature-card">
            <h3>🔧 遗留代码</h3>
            <p>在需要兼容旧版浏览器或处理已知范围内的字符时，fromCharCode仍然可用。</p>
            <div class="code-block">
              <span class="code-comment">// 基本ASCII字符</span>
              <span class="code-keyword">const</span> char = <span class="code-keyword">String</span>.fromCharCode(<span
                class="code-number">65</span>);
            </div>
          </div>
        </div>
      </div>

      <!-- Unicode示例 -->
      <div class="section">
        <h2>🔤 Unicode字符示例</h2>

        <div class="unicode-examples">
          <div class="unicode-item">
            <div class="unicode-char" id="char1">A</div>
            <div class="unicode-info">
              <div>ASCII: 65</div>
              <div>U+0041</div>
            </div>
          </div>
          <div class="unicode-item">
            <div class="unicode-char" id="char2">中</div>
            <div class="unicode-info">
              <div>CJK: 20013</div>
              <div>U+4E2D</div>
            </div>
          </div>
          <div class="unicode-item">
            <div class="unicode-char" id="char3">😀</div>
            <div class="unicode-info">
              <div>Emoji: 128512</div>
              <div>U+1F600</div>
            </div>
          </div>
          <div class="unicode-item">
            <div class="unicode-char" id="char4">🇺🇸</div>
            <div class="unicode-info">
              <div>Flag: 127482,127480</div>
              <div>U+1F1FA U+1F1F8</div>
            </div>
          </div>
        </div>
      </div>

      <!-- 交互式演示 -->
      <div class="section">
        <h2>🎮 交互式演示</h2>

        <div class="interactive-demo">
          <div class="input-group">
            <label for="codePointInput">输入Unicode码点 (十进制或十六进制):</label>
            <input type="text" id="codePointInput" placeholder="例如: 65, 0x4E2D, 128512" value="128512">
          </div>

          <button class="btn btn-primary" onclick="convertCodePoint()">转换</button>
          <button class="btn btn-secondary" onclick="clearInteractive()">清空</button>

          <div id="interactiveResult" class="result-display">输入码点并点击转换</div>
        </div>
      </div>

      <!-- 性能对比 -->
      <div class="section">
        <h2>⚡ 性能对比</h2>

        <div class="demo-section">
          <div class="demo-title">性能测试</div>
          <div class="code-block">
            <span class="code-comment">// 测试10000次转换的性能</span>
            <span class="code-keyword">const</span> iterations = <span class="code-number">10000</span>;

            <span class="code-comment">// fromCharCode 性能测试</span>
            <span class="code-keyword">const</span> start1 = performance.now();
            <span class="code-keyword">for</span> (<span class="code-keyword">let</span> i = <span
              class="code-number">0</span>; i < iterations; i++) { <span class="code-keyword">
              String</span>.fromCharCode(<span class="code-number">65</span>);
              }
              <span class="code-keyword">const</span> time1 = performance.now() - start1;

              <span class="code-comment">// fromCodePoint 性能测试</span>
              <span class="code-keyword">const</span> start2 = performance.now();
              <span class="code-keyword">for</span> (<span class="code-keyword">let</span> i = <span
                class="code-number">0</span>; i < iterations; i++) { <span class="code-keyword">
                String</span>.fromCodePoint(<span class="code-number">65</span>);
                }
                <span class="code-keyword">const</span> time2 = performance.now() - start2;

                console.log(<span class="code-string">`fromCharCode: ${time1.toFixed(2)}ms`</span>);
                console.log(<span class="code-string">`fromCodePoint: ${time2.toFixed(2)}ms`</span>);
          </div>
          <div class="result-display" id="performanceResult">点击"运行性能测试"查看结果</div>
          <button class="btn btn-primary" onclick="runPerformanceTest()">运行性能测试</button>
        </div>
      </div>

      <!-- 总结 -->
      <div class="section">
        <h2>📝 总结与建议</h2>

        <div class="feature-grid">
          <div class="feature-card">
            <h3>✅ 推荐使用 fromCodePoint</h3>
            <ul style="color: #666; line-height: 1.6; margin-left: 20px;">
              <li>支持完整的Unicode范围</li>
              <li>正确处理代理对</li>
              <li>更好的错误处理</li>
              <li>现代JavaScript标准</li>
            </ul>
          </div>

          <div class="feature-card">
            <h3>⚠️ 谨慎使用 fromCharCode</h3>
            <ul style="color: #666; line-height: 1.6; margin-left: 20px;">
              <li>仅支持BMP范围</li>
              <li>无法处理代理对</li>
              <li>静默错误处理</li>
              <li>遗留方法</li>
            </ul>
          </div>
        </div>

        <div class="warning-display">
          <strong>注意：</strong>在现代JavaScript开发中，建议优先使用 <code>String.fromCodePoint()</code>，除非有特殊的兼容性要求。
        </div>
      </div>
    </div>
  </div>

  <script>
    // 基本示例
    function runBasicExample() {
      const char1 = String.fromCharCode(65);
      const char2 = String.fromCodePoint(65);
      const char3 = String.fromCharCode(0x4E2D);
      const char4 = String.fromCodePoint(0x4E2D);

      const result = `基本ASCII字符:
fromCharCode(65): "${char1}"
fromCodePoint(65): "${char2}"

基本多字节字符:
fromCharCode(0x4E2D): "${char3}"
fromCodePoint(0x4E2D): "${char4}"`;

      document.getElementById('basicResult').textContent = result;
    }

    // 代理对示例
    function runSurrogateExample() {
      const emojiCode = 0x1F600;
      const fromCharCode = String.fromCharCode(emojiCode);
      const fromCodePoint = String.fromCodePoint(emojiCode);

      const result = `Emoji字符 (U+1F600):
fromCharCode: "${fromCharCode}" (长度: ${fromCharCode.length})
fromCodePoint: "${fromCodePoint}" (长度: ${fromCodePoint.length})

字符编码:
fromCharCode: ${Array.from(fromCharCode).map(c => c.charCodeAt(0).toString(16)).join(', ')}
fromCodePoint: ${fromCodePoint.charCodeAt(0).toString(16)}`;

      document.getElementById('surrogateResult').textContent = result;
    }

    // 错误处理示例
    function runErrorExample() {
      const invalidCode = 0x200000;
      let result = '无效码点处理 (0x200000):\n\n';

      // fromCharCode
      try {
        const result1 = String.fromCharCode(invalidCode);
        result += `fromCharCode: "${result1}" (静默处理)\n`;
      } catch (e) {
        result += `fromCharCode error: ${e.message}\n`;
      }

      // fromCodePoint
      try {
        const result2 = String.fromCodePoint(invalidCode);
        result += `fromCodePoint: "${result2}"\n`;
      } catch (e) {
        result += `fromCodePoint error: ${e.message}\n`;
      }

      document.getElementById('errorResult').textContent = result;
    }

    // 交互式转换
    function convertCodePoint() {
      const input = document.getElementById('codePointInput').value.trim();
      let codePoint;

      try {
        if (input.startsWith('0x')) {
          codePoint = parseInt(input, 16);
        } else {
          codePoint = parseInt(input, 10);
        }

        if (isNaN(codePoint)) {
          throw new Error('无效的码点');
        }

        const fromCharCode = String.fromCharCode(codePoint);
        const fromCodePoint = String.fromCodePoint(codePoint);

        const result = `码点: ${codePoint} (0x${codePoint.toString(16).toUpperCase()})
fromCharCode: "${fromCharCode}" (长度: ${fromCharCode.length})
fromCodePoint: "${fromCodePoint}" (长度: ${fromCodePoint.length})

字符编码:
fromCharCode: ${Array.from(fromCharCode).map(c => c.charCodeAt(0).toString(16).toUpperCase()).join(', ')}
fromCodePoint: ${fromCodePoint.charCodeAt(0).toString(16).toUpperCase()}`;

        document.getElementById('interactiveResult').textContent = result;

      } catch (e) {
        document.getElementById('interactiveResult').textContent = `错误: ${e.message}`;
      }
    }

    // 清空交互式演示
    function clearInteractive() {
      document.getElementById('codePointInput').value = '';
      document.getElementById('interactiveResult').textContent = '输入码点并点击转换';
    }

    // 性能测试
    function runPerformanceTest() {
      const iterations = 10000;

      // fromCharCode 性能测试
      const start1 = performance.now();
      for (let i = 0; i < iterations; i++) {
        String.fromCharCode(65);
      }
      const time1 = performance.now() - start1;

      // fromCodePoint 性能测试
      const start2 = performance.now();
      for (let i = 0; i < iterations; i++) {
        String.fromCodePoint(65);
      }
      const time2 = performance.now() - start2;

      const result = `性能测试结果 (${iterations}次迭代):
fromCharCode: ${time1.toFixed(2)}ms
fromCodePoint: ${time2.toFixed(2)}ms

差异: ${Math.abs(time1 - time2).toFixed(2)}ms
${time1 < time2 ? 'fromCharCode更快' : 'fromCodePoint更快'}`;

      document.getElementById('performanceResult').textContent = result;
    }

    // 页面加载时初始化Unicode示例
    window.addEventListener('load', function () {
      // 设置Unicode示例字符
      document.getElementById('char1').textContent = String.fromCodePoint(65);
      document.getElementById('char2').textContent = String.fromCodePoint(0x4E2D);
      document.getElementById('char3').textContent = String.fromCodePoint(0x1F600);
      document.getElementById('char4').textContent = String.fromCodePoint(0x1F1FA, 0x1F1F8);

      console.log('fromCharCode vs fromCodePoint 演示页面已加载完成');
    });
  </script>
</body>

</html>